package com.jxtc.enterprise.employee.controller;


import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jxtc.enterprise.common.config.CompanyPickupCodeConfigProperties;
import com.jxtc.enterprise.common.constants.MealTypeEnum;
import com.jxtc.enterprise.common.constants.OrderConstants;
import com.jxtc.enterprise.common.constants.OrderPaymentStatus;
import com.jxtc.enterprise.common.entity.*;
import com.jxtc.enterprise.common.exception.ModifyFailureException;
import com.jxtc.enterprise.common.mapper.*;
import com.jxtc.enterprise.common.service.DeliveryLockerOpenApiService;
import com.jxtc.enterprise.common.utils.Pair;
import com.jxtc.enterprise.common.utils.Result;
import com.jxtc.enterprise.common.utils.ResultCode;
import com.jxtc.enterprise.common.utils.ResultGenerator;
import com.jxtc.enterprise.common.vo.DeliverBoxApiParamVO;
import com.jxtc.enterprise.common.vo.DeliveryLockerOpenDoorParameterVO;
import com.jxtc.enterprise.common.vo.PaginationResponseVO;
import com.jxtc.enterprise.employee.dto.ChangePasswordDTO;
import com.jxtc.enterprise.employee.dto.OrderCountItemDTO;
import com.jxtc.enterprise.employee.dto.OrderDetailWithFoodInfoDTO;
import com.jxtc.enterprise.employee.dto.QueryOrderByStatusDTO;
import com.jxtc.enterprise.employee.vo.OrderStatisticVO;
import com.jxtc.enterprise.employee.vo.WxOrderInfoVO;
import com.jxtc.enterprise.employee.vo.WxOrderItemVO;
import com.wechat.pay.java.service.profitsharing.model.OrderStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 我的 页面
 */
@RestController
@RequestMapping("/employee/AboutMePage")
@RequiredArgsConstructor
@Slf4j
public class AboutMeController {

    private final UserMapper userMapper;

    private final UserOtherCompRelationMapper userOtherCompRelationMapper;

    private final CompanyMapper companyMapper;

    private final DepartmentMapper departmentMapper;

    private final OrderMapper orderMapper;

    private final OrderDetailMapper orderDetailMapper;

    private final FoodMenuMapper foodMenuMapper;

    private final MenuStoreConfigMapper menuStoreConfigMapper;

    private final CompanyPickupCodeConfigProperties companyPickupCodeConfigProperties;

    private final DeliveryLockerOpenApiService deliveryLockerOpenApiService;

    /**
     * 当一次打开一个柜门时，调用开柜门接口后，sleep 默认3秒，然后再查询开柜门状态
     */
    @Value("${locker.sleepMsBeforeQueryOpenOneDoorResult}")
    private final Integer sleepMsBeforeQueryOpenOneDoorResult = 500;

    @Operation(summary = "3.1 查询用户取餐地址", tags = {"3. 用户小程序-我的"})
    @GetMapping(value = "/queryUserPickupAddressInfo")
    public Result<?> queryUserPickupAddressInfo(String companyId, String phone) {
        String requestId = UUID.randomUUID().toString();
        log.info("requestId: {}, employee mini app view, /queryUserPickupAddressInfo, companyId: {}, phone: {}", requestId, companyId, phone);

        // 先查 companyId + phoneNumber 的记录在 user 表中是否存在
        User user = userMapper.selectOne(
                new LambdaQueryWrapper<User>()
                        .eq(User::getCompanyId, companyId)
                        .eq(User::getPhoneNumber, phone)
                        .eq(User::getDelFlag, false)
        );

        Result<JSONObject> result = new Result<>();
        if (user != null) {
            // 如果 user 记录存在，则说明在员工所属公司下单，走正常流程即可
            Department department = departmentMapper.selectOne(
                    new LambdaQueryWrapper<Department>()
                            .eq(Department::getCompanyId, companyId)
                            .eq(Department::getId, user.getDepartmentId())
                            .eq(Department::getDelFlag, false));

            result.setCode(ResultCode.SUCCESS);
            result.setMessage("success");
            JSONObject resultData = new JSONObject();
            resultData.put("userName", user.getUsername());
            resultData.put("phone", user.getPhoneNumber());
            resultData.put("depart", department.getName());
            resultData.put("pickUpAddress", department.getAddress());
            resultData.put("password", user.getPasswd());
            result.setData(resultData);
        } else {
            // 如果 companyId + phoneNumber 的记录在 user 表中不存在，则可能说明员工当前在出差公司点餐
            // 此时查询 user 表以及 userOtherCompanyRelation 表获取用户取餐地址

            LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>()
                    .eq(User::getPhoneNumber, phone)
                    .eq(User::getDelFlag, false)
                    // 根据用户记录创建时间降序排序，在可能存在的多个用户记录中，取最新的用户信息
                    .orderByDesc(User::getCreateTime);
            List<User> userList = userMapper.selectList(userLambdaQueryWrapper);
            if (CollectionUtils.isEmpty(userList)) {
                // 如果 user 表中获取不到以 phone 作为手机号的用户，直接返回错误提示信息即可
                log.warn("requestId: {}, employee mini app view, /queryUserPickupAddressInfo, user not exist, phone: {}", requestId, phone);
                result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
                result.setMessage("公司 " + companyId + " 中未找到手机号为 " + phone + " 的用户，请联系工作人员");
                return result;
            }

            if (userList.size() > 1) {
                // 如果 user 表中获取到多个以 phone 作为手机号的用户，存在脏数据，记录一下日志方便后续修复数据
                log.error("requestId: {}, employee mini app view, /queryUserPickupAddressInfo, 存在多条以 {} 为手机号的用户记录, userList: {}", requestId, userList.size(), JSONObject.toJSONString(userList));
            }

            // 取最新的用户记录
            User latestUser = userList.get(0);
            LambdaQueryWrapper<UserOtherCompRelation> userOtherCompRelationLambdaQueryWrapper = new LambdaQueryWrapper<UserOtherCompRelation>()
                    .eq(UserOtherCompRelation::getUserId, latestUser.getId())
                    .eq(UserOtherCompRelation::getOtherCompanyId, companyId)
                    .eq(UserOtherCompRelation::getDelFlag, false);
            UserOtherCompRelation userOtherCompRelation = userOtherCompRelationMapper.selectOne(userOtherCompRelationLambdaQueryWrapper);
            if (userOtherCompRelation == null) {
                // 如果 userOtherCompRelation 表中获取不到以 userId + otherCompanyId 作为查询条件的记录，直接返回错误提示信息即可
                log.warn("requestId: {}, employee mini app view, /queryUserPickupAddressInfo, userOtherCompRelation not exist, userId: {}, otherCompanyId: {}", requestId, latestUser.getId(), companyId);
                result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
                result.setMessage("公司 " + companyId + " 中未找到手机号为 " + phone + " 的用户，请联系工作人员");
                return result;
            }

            // 通过 userOtherCompRelation 表中记录的部门 ID 直接查询部门信息
            Department department = departmentMapper.selectById(userOtherCompRelation.getDepartmentId());

            result.setCode(ResultCode.SUCCESS);
            result.setMessage("success");
            JSONObject resultData = new JSONObject();
            resultData.put("userName", latestUser.getUsername());
            resultData.put("phone", latestUser.getPhoneNumber());
            resultData.put("depart", department.getName());
            resultData.put("pickUpAddress", department.getAddress());
            resultData.put("password", latestUser.getPasswd());
            result.setData(resultData);
        }

        log.info("requestId: {}, employee mini app view, /queryUserPickupAddressInfo, response: {}", requestId, JSONObject.toJSONString(result));
        return result;
    }

    @ApiResponse(responseCode = "200", description = "若响应体中 code 为 200 表示修改密码成功，data 字段为空")
    @ApiResponse(responseCode = "200", description = "若响应体中 code 为 400 表示修改密码失败，message 字段则会提供错误信息")
    @Operation(summary = "3.6 修改密码", tags = {"3. 用户小程序-我的"})
    @PutMapping("/changePassword")
    public Result<?> changePassword(@Valid @RequestBody ChangePasswordDTO dto) {
        log.info("/changePassword, companyId: {}, phone: {}, password: {}", dto.getCompanyId(), dto.getPhone(), dto.getPassword());
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<User>()
                .eq(User::getCompanyId, dto.getCompanyId())
                .eq(User::getPhoneNumber, dto.getPhone())
                .eq(User::getDelFlag, false);

        User user = new User();
        user.setPasswd(dto.getPassword());

        if (userMapper.update(user, updateWrapper) <= 0) {
            throw new ModifyFailureException("手机号为 " + dto.getPhone() + " 的用户密码修改失败，请联系工作人员");
        }

        return ResultGenerator.genSuccessResult();
    }

    /**
     * 不支持用户修改取餐地址，只能向HR申请，由HR通过 HR页面进行修改
     *
     * @return
     */
    @Operation(summary = "3.2 保存用户取餐地址", tags = {"3. 用户小程序-我的"})
    @PutMapping(value = "/updateUserPickupAddressInfo")
    public Result<?> updateUserPickupAddressInfo(@RequestBody JSONObject requestBody) {

        // todo 不支持此功能
        return null;
    }

    @ApiResponse(responseCode = "200", description = "若响应体中 code 为 200 表示查询订单信息成功，data 字段为订单分页相关的信息")
    @ApiResponse(responseCode = "200", description = "若响应体中 code 为 400 表示查询订单信息失败，message 字段则会提供错误信息")
    @Operation(summary = "4.3.3 查看各种状态的订单", tags = {"2. 用户小程序-下单"})
    @PostMapping(value = "/queryOrdersByStatus")
    public Result<PaginationResponseVO<WxOrderInfoVO>> queryOrdersByStatus(@Valid @RequestBody QueryOrderByStatusDTO dto) {
        String requestId = UUID.randomUUID().toString();
        log.info("requestId: {}, employee mini app view, /queryOrdersByStatus, companyId: {}, phone: {}, status: {}", requestId, dto.getCompanyId(), dto.getPhone(), dto.getStatus());

        // 通过用户手机号获取用户信息以及可点餐的公司 ID 列表
        Pair<User, List<String>> userAndCompanyIdListPair = queryUserInfoAndCanOrderFoodCompanyIdList(dto.getPhone());
        User user = userAndCompanyIdListPair.getLeft();
        List<String> userCanOrderFoodCompanyIdList = userAndCompanyIdListPair.getRight();
        if (user == null || CollectionUtils.isEmpty(userCanOrderFoodCompanyIdList)) {
            // 1. 如果仅通过手机号查询不到用户信息，说明该手机号所在的用户确实不存在
            // 2， 如果用户可点餐公司 ID 列表为空，说明该用户信息也不存在，因为用户可点餐公司至少需要包含用户所属公司
            // 这两种情况直接向前端返回错误提示信息即可
            log.warn("requestId: {}, employee mini app view, /queryOrdersByStatus, user not found, phone: {}, userAndCompanyIdListPair: {}",
                    requestId, dto.getPhone(), JSONObject.toJSONString(userAndCompanyIdListPair));
            throw new IllegalArgumentException("手机号为 " + dto.getPhone() + " 的用户不存在，请联系管理员处理");
        }

        if (!userCanOrderFoodCompanyIdList.contains(dto.getCompanyId())) {
            // 如果用户可点餐公司 ID 列表中不包含当前查看订单状态时，前端传递过来的 companyId
            // 说明当前用户不在该 companyId 对应的公司中，直接向前端返回错误提示信息即可
            log.warn("requestId: {}, employee mini app view, /queryOrdersByStatus, companyId 不在可点餐公司列表种, phone: {}, userCanOrderFoodCompanyIdList: {}, dto.companyId: {}",
                    requestId, dto.getPhone(), JSONObject.toJSONString(userCanOrderFoodCompanyIdList), dto.getCompanyId());
            throw new IllegalArgumentException("手机号为 " + dto.getPhone() + " 的用户不在公司 ID 为 " + dto.getCompanyId() + " 的公司中，请联系管理员处理");
        }

        // 获取用户 ID
        String userId = user.getId();

        LambdaQueryWrapper<OrderPayment> orderQueryWrapper = new LambdaQueryWrapper<OrderPayment>()
                .eq(OrderPayment::getUserId, userId)
                // 查询用户订单时，由于 OrderPayment 表中记录的 ID 即为用户下单时所用公司 ID
                // 为了可以看到该用户的所有订单数据（所属公司 + 其它可点餐公司），这里通过用户可点餐公司 ID 列表筛选即可
                .in(OrderPayment::getCompanyId, userCanOrderFoodCompanyIdList)
                .eq(OrderPayment::getPhone, dto.getPhone())
                .orderByDesc(OrderPayment::getCreateTime);

        // 如果查询的订单状态字段是小于 0 的，认为查询的是所有订单，此时不需要拼接 status = #{status}
        // 如果查询的订单状态是大于 0 的，此时需要拼接 status = #{status}, dto.status 的值应与 OrderPaymentStatus 中定义的值匹配
        if (dto.getStatus() > 0) {
            orderQueryWrapper.eq(OrderPayment::getStatus, dto.getStatus());
        }

        // 分页查询数据，防止一次性查询太多数据
        Page<OrderPayment> page = new Page<>(dto.getPage(), dto.getSize());
        Page<OrderPayment> orderPaymentPage = orderMapper.selectPage(page, orderQueryWrapper);

        List<WxOrderInfoVO> records = new ArrayList<>();
        List<OrderPayment> orderList = orderPaymentPage.getRecords();

        // 如果查询到的订单数量大于 0，则需要查询每个订单的订单详情
        if (!orderList.isEmpty()) {
            List<String> orderIds = orderList.stream().map(OrderPayment::getId).collect(Collectors.toList());

            // 根据订单 ID 列表查询所有的订单详情记录
            List<OrderDetailWithFoodInfoDTO> orderDetailWithFoodInfoList = orderDetailMapper.listByOrderIds(orderIds);

            // 将查询到的包含菜品信息的订单详情数据按照 orderId 进行分组
            Map<String, List<OrderDetailWithFoodInfoDTO>> orderItemMap = orderDetailWithFoodInfoList.stream()
                    .collect(Collectors.groupingBy(OrderDetailWithFoodInfoDTO::getOrderId));

            // 由于查询到的订单详情中，所有公司 ID 一定来自于 userCanOrderFoodCompanyIdList
            // 所以可以通过 userCanOrderFoodCompanyIdList 查询到订单中所有公司信息
            LambdaQueryWrapper<Company> companyIdAndNameQueryWrapper = new LambdaQueryWrapper<Company>()
                    .in(Company::getId, userCanOrderFoodCompanyIdList)
                    .select(Company::getId, Company::getName);
            List<Company> companyList = companyMapper.selectList(companyIdAndNameQueryWrapper);
            Map<String, String> companyIdToNameMap = companyList.stream().collect(Collectors.toMap(Company::getId, Company::getName));

            for (OrderPayment order : orderList) {
                WxOrderInfoVO wxOrderInfoVO = new WxOrderInfoVO();
                wxOrderInfoVO.setOrderId(order.getId());
                // totalAmount 记录的是订单实际销售价格
                // wxOrderInfoVO.setTotalAmount(order.getTotalAmount());

                wxOrderInfoVO.setDeductionPrice(order.getDeductionPrice());
                wxOrderInfoVO.setUserPaid(order.getUserPaid());
                // 通过 OrderPaymentStatus 枚举将 order_payment 表中的 status 字段转换为对应的中文名称
                wxOrderInfoVO.setOrderStatus(OrderPaymentStatus.getNameByValue(order.getStatus()));

                // 根据订单 ID 获取对应的订单详情数据，并将其转换成 WxOrderItemVO 对象
                List<OrderDetailWithFoodInfoDTO> orderDetailList = orderItemMap.get(order.getId());
                if (orderDetailList == null || orderDetailList.isEmpty()) {
                    // orderId 在 order_detail 表中没有对应的数据时，表明该项 orderId 是脏数据
                    // 跳过该订单
                    continue;
                }
                // 前面代码已经校验过了，orderDetailList 不会为null
                List<WxOrderItemVO> orderItems = orderDetailList.stream()
                        .map(this::buildWxOrderItemVO)
                        .collect(Collectors.toList());
                wxOrderInfoVO.setTotalNum(orderItems.size());
                // 获取外卖柜编号信息时，由于外卖柜时跟下单公司所关联的，在出差公司下单也应该使用出差公司 ID，所以这里直接使用前端传递过来的公司 ID（dto.getCompanyId()）即可
                buildLockerBoxNoFromLockerInfo(dto.getCompanyId(), orderDetailList, wxOrderInfoVO, order.getStatus());
                String formattedDateTime = DateUtil.format(order.getCreateTime(), "yyyy-MM-dd HH:mm:ss");
                wxOrderInfoVO.setOrderTime(formattedDateTime);

                wxOrderInfoVO.setItems(orderItems);

                int totalShowPrice = 0;
                for (OrderDetailWithFoodInfoDTO item : orderDetailList) {
                    totalShowPrice += item.getShowPrice() * item.getQuantity();
                }
                wxOrderInfoVO.setTotalAmount(totalShowPrice);

                // 暂时禁用 "再来一单" 按钮
                // 删除用于判断是否可以使用 "再来一单" 功能的相关逻辑，判断逻辑参见 commit: f0423ae38f910064cdfd51a0085a99290e5fa509
                wxOrderInfoVO.setCanSelectFlag(4);
                wxOrderInfoVO.setCanSelectMessage("再来一单功能已禁用");

                // 根据 "配置文件" 判断该公司的 "取餐码" 是否显示
                // pickUpCode
                // pickUpCodeShowFlag, 1 不显示，2 显示
                int pickUpCodeShowFlag = 1;
                if (companyPickupCodeConfigProperties.getEnabledIds().contains(order.getCompanyId())) {
                    pickUpCodeShowFlag = 2;
                }

                // 一份订单可能有多个订单详情，一个订单详情有一个取餐码
                // 为了方便前端仅展示一个取餐码，此处取最大的取餐码
                Integer pickupCode = orderDetailList.stream()
                        .map(OrderDetailWithFoodInfoDTO::getPickupCode)
                        .max(Comparator.comparingInt(Integer::intValue))
                        .orElse(0);
                if (pickupCode == 0) {
                    log.error("订单取餐码异常，orderId: {}", orderIds);
                }

                wxOrderInfoVO.setPickUpCode(pickupCode);
                wxOrderInfoVO.setPickUpCodeShowFlag(pickUpCodeShowFlag);

                // 当前下单地址仅显示下单公司名称即可
                String companyName = companyIdToNameMap.get(order.getCompanyId());
                if (StringUtils.isBlank(companyName)) {
                    log.warn("requestId: {}, employee mini app view, /queryOrdersByStatus, OrderPayment 中记录的 companyId 在 Company 表中不存在，orderId: {}, companyId: {}",
                            requestId, order.getId(), order.getCompanyId());
                    // 通过公司 ID 找不到公司名称时，返回未知的公司名称，尽早暴露数据不一致问题
                    companyName = "未知的公司名称";
                }
                wxOrderInfoVO.setCreateOrderAddress(companyName);

                records.add(wxOrderInfoVO);
            }
        }

        // 返回分页后的数据
        PaginationResponseVO<WxOrderInfoVO> vo = new PaginationResponseVO<>(orderPaymentPage.getTotal(), orderPaymentPage.getCurrent(), records);
        log.info("requestId: {}, employee mini app view, /queryOrdersByStatus, response: {}", requestId, JSONObject.toJSONString(vo));
        return ResultGenerator.genSuccessResult(vo);
    }

    /**
     * 通过用户手机号获取该用户信息以及可点餐的公司 ID 列表（所属公司 ID + 其它可点餐公司 ID 列表）
     */
    private Pair<User, List<String>> queryUserInfoAndCanOrderFoodCompanyIdList(String userPhone) {
        LambdaQueryWrapper<User> userQueryByPhoneWrapper = new LambdaQueryWrapper<User>()
                .eq(User::getPhoneNumber, userPhone)
                .eq(User::getDelFlag, false)
                .orderByDesc(User::getCreateTime);
        List<User> userList = userMapper.selectList(userQueryByPhoneWrapper);
        if (CollectionUtils.isEmpty(userList)) {
            // 如果仅通过手机号查询不到用户信息，说明该手机号所在的用户确实不存在
            // 这种情况直接返回空列表即可，不存在可点餐公司
            return Pair.of(null, Collections.emptyList());
        }

        // 取最新的用户信息
        User latestUser = userList.get(0);
        // 获取用户所属公司 ID
        String userBelongToCompanyId = latestUser.getCompanyId();

        // 查询用户其它可点餐的公司 ID 列表
        LambdaQueryWrapper<UserOtherCompRelation> userOtherCompRelationLambdaQueryWrapper = new LambdaQueryWrapper<UserOtherCompRelation>()
                .eq(UserOtherCompRelation::getUserId, latestUser.getId())
                .eq(UserOtherCompRelation::getDelFlag, false)
                .select(UserOtherCompRelation::getOtherCompanyId);
        List<String> userOtherCanOrderFoodCompanyIdList = userOtherCompRelationMapper.selectObjs(userOtherCompRelationLambdaQueryWrapper);
        if (CollectionUtils.isEmpty(userOtherCanOrderFoodCompanyIdList)) {
            // 如果用户没有其它可点餐的公司，则直接返回用户所属公司 ID 作为可点餐公司列表即可
            return Pair.of(latestUser, Collections.singletonList(userBelongToCompanyId));
        }

        // 如果用户有其它可点餐的公司，则将用户所属公司 ID 和其它可点餐的公司 ID 合并返回
        List<String> userCanOrderFoodCompanyIdList = new ArrayList<>(userOtherCanOrderFoodCompanyIdList.size() + 1);
        userCanOrderFoodCompanyIdList.add(userBelongToCompanyId);
        userCanOrderFoodCompanyIdList.addAll(userOtherCanOrderFoodCompanyIdList);
        return Pair.of(latestUser, userCanOrderFoodCompanyIdList);
    }

    private WxOrderItemVO buildWxOrderItemVO(OrderDetailWithFoodInfoDTO dto) {
        JSONArray foodSpecification = JSONUtil.parseArray(dto.getFoodSpecification());
        // 当前版本在 foodName 之前拼接用餐时段 mealType 即可
        String mealType = getMealTypeChineseNameFromEnum(dto.getMealType());
        String foodNameWithMealType = String.format("(%s) %s", mealType, dto.getFoodPrintName());
        return new WxOrderItemVO(
                dto.getStoreName(),
                foodNameWithMealType,
                dto.getFoodPrintName(),
                dto.getSalePrice(),
                dto.getShowPrice(),
                dto.getQuantity(),
                dto.getImage(),
                foodSpecification,
                dto.getNote()
        );
    }

    /**
     * 将数据库中保存的用餐时段的英文名称转换成对应的中文名称
     *
     * @param mealTypeEn 用餐时段的英文名称
     * @return 中文名称
     */
    private String getMealTypeChineseNameFromEnum(String mealTypeEn) {
        return Arrays.stream(MealTypeEnum.values())
                .filter(mealTypeEnum -> mealTypeEnum.getMealTypeEn().equals(mealTypeEn))
                .map(MealTypeEnum::getMealTypeCn)
                .findAny()
                .orElse("");
    }

    /**
     * @return
     */
    @Operation(summary = "3.4 查看交易明细", tags = {"2. 用户小程序-下单"})
    @PostMapping(value = "/queryBalanceTransactions")
    public Result<?> queryBalanceTransactions(@RequestBody HashMap<String, Object> requestBody) {

        Result<String> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData("success");

        return result;
    }

    @ApiResponse(responseCode = "200", description = "若响应体中 code 为 200 表示获取订单信息成功，data 字段为各状态订单的数量")
    @ApiResponse(responseCode = "200", description = "若响应体中 code 为 400 表示获取订单信息失败，message 字段则会提供错误信息")
    @Parameter(name = "phone", description = "用户手机号", example = "18100001111")
    @Parameter(name = "companyId", description = "公司 ID", example = "565a416545cc196d005439407b953fdd")
    @Operation(summary = "3.5 获取各种状态订单的数量", tags = {"2. 用户小程序-下单"})
    @GetMapping("/queryOrderStatisticInfo")
    public Result<OrderStatisticVO> queryOrderStatisticInfo(@RequestParam String phone, @RequestParam String companyId) {
        String requestId = UUID.randomUUID().toString();
        log.info("requestId: {}, employee mini app view, /queryOrderStatisticInfo request, phone: {}, companyId: {}", requestId, phone, companyId);

        // 通过用户手机号获取用户信息以及可点餐的公司 ID 列表
        Pair<User, List<String>> userAndCompanyIdListPair = queryUserInfoAndCanOrderFoodCompanyIdList(phone);
        User user = userAndCompanyIdListPair.getLeft();
        List<String> userCanOrderFoodCompanyIdList = userAndCompanyIdListPair.getRight();
        if (user == null || CollectionUtils.isEmpty(userCanOrderFoodCompanyIdList)) {
            // 1. 如果仅通过手机号查询不到用户信息，说明该手机号所在的用户确实不存在
            // 2， 如果用户可点餐公司 ID 列表为空，说明该用户信息也不存在，因为用户可点餐公司至少需要包含用户所属公司
            // 这两种情况直接向前端返回错误提示信息即可
            log.warn("requestId: {}, employee mini app view, /queryOrderStatisticInfo, user not found, phone: {}, userAndCompanyIdListPair: {}",
                    requestId, phone, JSONObject.toJSONString(userAndCompanyIdListPair));
            throw new IllegalArgumentException("手机号为 " + phone + " 或公司 ID 为" + companyId + " 的用户不存在，请联系管理员处理");
        }

        if (!userCanOrderFoodCompanyIdList.contains(companyId)) {
            // 如果用户可点餐公司 ID 列表中不包含当前查看订单状态时，前端传递过来的 companyId
            // 说明当前用户不在该 companyId 对应的公司中，直接向前端返回错误提示信息即可
            log.warn("requestId: {}, employee mini app view, /queryOrdersByStatus, companyId 不在可点餐公司列表种, phone: {}, userCanOrderFoodCompanyIdList: {}, companyId: {}",
                    requestId, phone, JSONObject.toJSONString(userCanOrderFoodCompanyIdList), companyId);
            throw new IllegalArgumentException("手机号为 " + phone + " 的用户不在公司 ID 为 " + companyId + " 的公司中，请联系管理员处理");
        }

        String userId = user.getId();

        // OrderPayment 和 OrderDetail 表中保存的公司 ID 为用户下单时所用公司 ID，故使用前端传递过来的公司 ID 即可
        List<OrderCountItemDTO> orderCountItemList = orderMapper.queryOrderCount(userId, userCanOrderFoodCompanyIdList, phone);

        // 将查询到的数据转换成 status: count 键值对方便取值
        Map<Integer, Integer> orderStatusCountMap = orderCountItemList.stream()
                .filter(item -> item.getStatus() != null && item.getNum() != null) // 过滤可能出现的脏数据
                .collect(Collectors.toMap(OrderCountItemDTO::getStatus, OrderCountItemDTO::getNum));

        // 计算各状态订单数量总和
        Integer total = orderCountItemList.stream().map(OrderCountItemDTO::getNum).reduce(0, Integer::sum);

        Integer toPay = orderStatusCountMap.getOrDefault(OrderPaymentStatus.WAIT.getValue(), 0);
        Integer toSend = orderStatusCountMap.getOrDefault(OrderPaymentStatus.TO_SEND.getValue(), 0);
        Integer toReceive = orderStatusCountMap.getOrDefault(OrderPaymentStatus.TO_RECEIVE.getValue(), 0);
        Integer toReview = orderStatusCountMap.getOrDefault(OrderPaymentStatus.TO_REVIEW.getValue(), 0);
        Integer complete = orderStatusCountMap.getOrDefault(OrderPaymentStatus.COMPLETED.getValue(), 0);

        OrderStatisticVO vo = new OrderStatisticVO(total, toPay, toSend, toReceive, toReview, complete);

        return ResultGenerator.genSuccessResult(vo);
    }

    /**
     * 2.6.9 通过订单 ID 打开该订单所分配的外卖柜
     *
     * @param orderId 订单 ID
     */
    @GetMapping("/openDeliveryLockerBoxByOrderId")
    public Result<Void> openDeliveryLockerBoxByOrderId(@RequestParam(value = "orderId") String orderId) {
        log.info("/openDeliveryLockerBoxByOrderId by_user_from_miniApp, orderId: {}", orderId);
        LambdaQueryWrapper<OrderDetail> orderDetailWrapper = new LambdaQueryWrapper<OrderDetail>()
                .eq(OrderDetail::getOrderId, orderId)
                .select(OrderDetail::getId, OrderDetail::getLockerInfo, OrderDetail::getLockerBoxNo)
                .orderByAsc(OrderDetail::getLockerBoxNo);
        List<OrderDetail> orderDetailList = orderDetailMapper.selectList(orderDetailWrapper);
        if (CollectionUtils.isEmpty(orderDetailList)) {
            log.warn("/openDeliveryLockerBoxByOrderId, 未找到对应的订单详情, orderId: {}", orderId);
            return ResultGenerator.genFailResult("ID 为 " + orderId + " 的订单详情不存在");
        }

        List<OrderDetail> needToOpenDoorList = orderDetailList.stream()
                .filter(orderDetail -> org.springframework.util.StringUtils.hasText(orderDetail.getLockerInfo()))
                .collect(Collectors.toList());

        log.info("/openDeliveryLockerBoxByOrderId by_user_from_miniApp, 有 {} 个外卖柜窗口需要打开", needToOpenDoorList.size());
        int callOpenDoorApiCount = 0;
        for (OrderDetail orderDetail : needToOpenDoorList) {
            DeliverBoxApiParamVO boxApiParamVO = JSONObject.parseObject(orderDetail.getLockerInfo(), DeliverBoxApiParamVO.class);

            String clientNo = boxApiParamVO.getClientNo();
            // 这里直接使用订单详情 ID 作为 orderNo，每笔订单详情 ID 一定是不重复的
            String orderNo = orderDetail.getId();
            String appId = boxApiParamVO.getAppId();
            String appKey = boxApiParamVO.getAppKey();
            Integer cab = boxApiParamVO.getCab();
            Integer col = boxApiParamVO.getCol();

            DeliveryLockerOpenDoorParameterVO openDoorVo = new DeliveryLockerOpenDoorParameterVO();
            openDoorVo.setOrderNo(orderNo);
            openDoorVo.setCab(String.valueOf(cab));
            // 依次调用打开柜门的接口，所以每一打开一个柜门，sn 一直为 1
            openDoorVo.setSn("1");
            openDoorVo.setCol(String.valueOf(col));
            openDoorVo.setPrice("0.00");
            openDoorVo.setNo(orderDetail.getLockerBoxNo());
            openDoorVo.setName("");
            openDoorVo.setType("5");

            // 调用打开柜门接口
            deliveryLockerOpenApiService.openDoor(clientNo, orderNo, appId, appKey, openDoorVo, sleepMsBeforeQueryOpenOneDoorResult);
            callOpenDoorApiCount++;
        }
        log.info("/openDeliveryLockerBoxByOrderId, 调用打开柜门接口 {} 次", callOpenDoorApiCount);
        return ResultGenerator.genSuccessResult();
    }

    /**
     * 从 order_detail 表中读取分配给订单中 该菜品的 外卖柜编号，通常不会有问题，异常情况下，如果分配的是 默认的32个全0的编号，返回 00-0-00，避免返回字符串太长，导致小程序显示问题
     *
     * @param orderDetailList
     * @return
     */
    private void buildLockerBoxNoFromLockerInfo(String companyId, List<OrderDetailWithFoodInfoDTO> orderDetailList, WxOrderInfoVO wxOrderInfoVO, int orderStatus) {

        // 外层代码已经校验过了，orderDetailList 不会为 null，也不会为 空列表
        String lockerBoxNo = orderDetailList.get(0).getLockerBoxNo();
        String mealType = orderDetailList.get(0).getMealType();
        String dateFlag = orderDetailList.get(0).getDateFlag();
        // 正常情况下，无外卖柜的公司的订单，order_detail 表中的 locker_box_no 为 00-0-00，但是 order_detail 表的默认值为32个0，两者是等效的，00-0-00是为了展示的时候，避免过多字符导致页面显示变形
        // 此处对 00-0-00 和 32个0，都认为是 无效的或没有外卖柜的订单，不显示 【开柜门】按钮
        if (OrderConstants.DEFAULT_LOCKER_BOX_NO.equalsIgnoreCase(lockerBoxNo)
                || OrderConstants.SHORT_DEFAULT_LOCKER_BOX_NO.equalsIgnoreCase(lockerBoxNo)
                || OrderConstants.NO_LOCKER_BOX_NO.equalsIgnoreCase(lockerBoxNo)) {

            if (OrderConstants.NO_LOCKER_BOX_NO.equalsIgnoreCase(lockerBoxNo)) {
                // 如果是 "柜外"，则显示 编码+按钮，但是按钮不可点击
                wxOrderInfoVO.setLockerBoxCode(OrderConstants.NO_LOCKER_BOX_NO);
                wxOrderInfoVO.setShowOpenLockerBtn(true);
                wxOrderInfoVO.setLockerBoxBtnEnabled(false);
            } else {
                // 如果订单对应的公司，没有提供外卖柜，则 标签+按钮不显示，返回的 lockerBoxCode 没有效果，不会显示
                wxOrderInfoVO.setLockerBoxCode(OrderConstants.SHORT_DEFAULT_LOCKER_BOX_NO);
                wxOrderInfoVO.setLockerBoxBtnEnabled(false);
                wxOrderInfoVO.setShowOpenLockerBtn(false);
            }
        } else {
            // 只要有外卖柜编号，按钮都可见，但是不一定可以点击
            wxOrderInfoVO.setLockerBoxCode(lockerBoxNo);

            if (MealTypeEnum.LUNCH.getMealTypeEn().equalsIgnoreCase(mealType)) {
                // 针对罗兄需求定制的规则，如果是午餐，用餐当天18点之前，按钮都可见，且可点击
                evalLockerBoxBtnStatusByFixedHourMinute(wxOrderInfoVO, dateFlag,18, 0);
            } else {
                // 其他用餐时段，则按照 送达时间 + 4 小时进行判断
                evalLockerBoxBtnStatusByDeliveryTimeAndFixedHour(wxOrderInfoVO, companyId, mealType, dateFlag, 4);
            }
        }
    }

    private boolean checkIsBefore4Hour(String deliveryTimeStr, int additionalHours) {
        // 默认值为true，这样，如果在下面代码中出现异常，则返回结果也是true，为true 则按钮可见，也可以点击，尽量保证按钮对用户可见，可以点击，
        // 若默认值为false，则由于代码或其他因素导致的异常，导致按钮不可用，则会有影响用户使用；尽可能的保证按钮对用户可用
        boolean isBefore4Hour = true;
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime now = null;
        LocalDateTime deliveryTimePlus4Hour = null;
        try {
            // 解析字符串为LocalDateTime
            LocalDateTime deliveryTime = LocalDateTime.parse(deliveryTimeStr, formatter);
            // 获取当前时间
            now = LocalDateTime.now();
            // 计算deliveryTime + 4小时，送达时间 + 4 小时之内，可以点击，之后不可点击
            deliveryTimePlus4Hour = deliveryTime.plusHours(additionalHours);
            // 判断当前时间是否在 deliveryTime + 4小时之前
            isBefore4Hour = now.isBefore(deliveryTimePlus4Hour);
        } catch (Exception e) {
            System.out.println("时间格式解析错误: " + e.getMessage());
        }

        log.info("checkIsBefore4Hour now: {}, deliveryTime_plus_4_hour: {}, return: {}", now, deliveryTimePlus4Hour, isBefore4Hour);
        return isBefore4Hour;
    }

    /**
     * 针对罗兄需求定制的规则，如果是午餐，用餐当天18点之前，按钮都可见，且可点击
     *
     * 订单状态为已送达 且 时间在 18点之后，按钮可见，但不可点击
     * 订单状态不是 已送达，或 时间在 18点之前，按钮都可见，且可点击
     * @param wxOrderInfoVO
     * @param hourOf24
     * @param minute
     */
    private void evalLockerBoxBtnStatusByFixedHourMinute(WxOrderInfoVO wxOrderInfoVO, String dateFlag, int hourOf24, int minute) {

        // dateFlag 格式为 yyyy-MM-dd 比如 2025-10-01
        if (org.springframework.util.StringUtils.hasText(dateFlag)) {

            LocalDateTime currentTime = LocalDateTime.now(); // 获取当前时间
            LocalDateTime targetTime = evalTargetTimeByDateFlagAnd18hour(wxOrderInfoVO.getOrderId(), dateFlag, hourOf24, minute);
            if (targetTime == null) {
                // 当dateFlag异常时，按钮可以点击，这样即使存在bug 或者 订单数据中的dateFlag 字段值有问题，也可以保证用户能够正常开柜门
                wxOrderInfoVO.setShowOpenLockerBtn(true);
                wxOrderInfoVO.setLockerBoxBtnEnabled(true);
                log.error("evalLockerBoxBtnStatusByFixedHourMinute dateFlag invalid: {}, orderId: {}, lockerBoxButton show and enabled", dateFlag, wxOrderInfoVO.getOrderId());
            } else {
                boolean isAfter18 = currentTime.isAfter(targetTime); // 判断当前时间是否在 dataFlag 18:00:00 之后
                if (isAfter18) {
                    // 当前时间在该订单的dateFlag，时分秒为 18:00:00 之后，仅显示按钮，不可点击; 没份订单的dateFlag不同，此处的 targetTime 也不同，因此历史订单也不会有问题
                    wxOrderInfoVO.setShowOpenLockerBtn(true);
                    wxOrderInfoVO.setLockerBoxBtnEnabled(false);
                } else {
                    // 当前时间在该订单的dateFlag，时分秒为 18:00:00 之前，显示按钮，并可点击; 没份订单的dateFlag不同，此处的 targetTime 也不同，因此历史订单也不会有问题
                    wxOrderInfoVO.setShowOpenLockerBtn(true);
                    wxOrderInfoVO.setLockerBoxBtnEnabled(true);
                }
            }
        } else {
            // 当dateFlag异常时，按钮可以点击，这样即使存在bug，也可以保证用户能够正常开柜门
            wxOrderInfoVO.setShowOpenLockerBtn(true);
            wxOrderInfoVO.setLockerBoxBtnEnabled(true);
            log.error("evalLockerBoxBtnStatusByFixedHourMinute dateFlag is null or empty, orderId: {}, lockerBoxButton show and enabled", wxOrderInfoVO.getOrderId());
        }
    }

    /**
     * 根据每个订单不同的 dateFlag，计算出 该午餐订单，用餐当天18点的 时间对象实例，用于 和当前时间进行比较
     * @param orderId
     * @param dateFlag
     * @param hourOf24
     * @param minute
     * @return
     */
    private LocalDateTime evalTargetTimeByDateFlagAnd18hour(String orderId, String dateFlag, int hourOf24, int minute) {

        LocalDateTime targetTime = null;
        // Integer.parseInt 会抛出异常
        try {
            String[] parts = dateFlag.split("-");
            int year = Integer.parseInt(parts[0]);
            int month = Integer.parseInt(parts[1]);
            int day = Integer.parseInt(parts[2]);
            targetTime = LocalDateTime.of(year, month, day, hourOf24, minute, 0); // 定义18:00的时间
        } catch (Exception ex) {
            log.error("evalTargetTimeByDateFlagAnd18hour, orderId: {}, dateFlag: {}, targetTime set null", orderId, dateFlag);
        }

        return targetTime;
    }

    private void evalLockerBoxBtnStatusByDeliveryTimeAndFixedHour(WxOrderInfoVO wxOrderInfoVO, String companyId, String mealType, String dateFlag, int additionalHours) {

        // todo 需要根据当前时间判断是否可以点击， dateFlag+mealType 以及 送达时间+2 小时时候，不可以点击
        List<MenuStoreConfig> menuStoreConfigs = menuStoreConfigMapper.selectList(
                new LambdaQueryWrapper<MenuStoreConfig>()
                        .eq(MenuStoreConfig::getCompanyId, companyId)
                        .eq(MenuStoreConfig::getMealType, mealType)
                        .eq(MenuStoreConfig::getDelFlag, false));
        if (CollectionUtils.isEmpty(menuStoreConfigs)) {
            // 当无法判断 开柜门 按钮是否可以点击时，默认 展示按钮，且按钮可以点击，虽然可能当前时间用户不应该点击，但是尽量保证用户可以点击的时候，能够点击
            wxOrderInfoVO.setShowOpenLockerBtn(true);
            wxOrderInfoVO.setLockerBoxBtnEnabled(true);
            log.error("can't find delivery_time for companyId: {}, mealType:{}, lockerBox button show and enabled", companyId, mealType);
        } else {
            String deliveryTime = menuStoreConfigs.get(0).getDeliveryTime();
            int count = StringUtils.countMatches(deliveryTime, ":");
            if (count == 1) {
                deliveryTime += ":00";
                log.info("append :00 for delivery_time of companyId:{}, mealType: {}", companyId, mealType);
            }

            String formattedDeliveryTime = MessageFormat.format("{0} {1}", dateFlag, deliveryTime);
            boolean isBefore4Hour = checkIsBefore4Hour(formattedDeliveryTime, additionalHours);
            if (isBefore4Hour) {
                // 在 当天某一餐的送达时间+ additionalHours 小时之前，按钮可见，也可以点击
                wxOrderInfoVO.setShowOpenLockerBtn(true);
                wxOrderInfoVO.setLockerBoxBtnEnabled(true);
            } else {
                // 在 当天某一餐的送达时间+2小时之后，按钮可见，单不可以点击
                wxOrderInfoVO.setShowOpenLockerBtn(true);
                wxOrderInfoVO.setLockerBoxBtnEnabled(false);
            }
        }
    }

}
